Limpieza de datos#
Librerias#
# Librerias
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import os
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from fuzzywuzzy import process
# Definir la paleta de colores personalizada
custom_colors = ['#1F3040', '#B9CDCA', '#F2C6AC', '#D99982', '#735749']
color_histograma = custom_colors[3]
color_linea = custom_colors[4]
Cargar datos#
## Ruta de la carpeta que contiene los archivos CSV
#carpeta = 'C:/UNINORTE/VC/Proyecto2/dataset_ventas'
##Obtener la lista de archivos CSV en la carpeta
#archivos_csv = [archivo for archivo in os.listdir(carpeta) if archivo.endswith('.csv')]
## Crear un DataFrame vacío para almacenar los datos combinados
#ventas = pd.DataFrame()
## Leer cada archivo CSV y combinarlo en el DataFrame datos_combinados
#for archivo in archivos_csv:
# ruta_archivo = os.path.join(carpeta, archivo)
# datos_archivo = pd.read_csv(ruta_archivo, index_col=0)
# ventas = pd.concat([ventas, datos_archivo], ignore_index=True)
## Descargar los datos en un excel
#ventas.to_excel('datos.xlsx', index=False)
Después de fusionar todos los documentos CSV, se procede a descargar el archivo final. A partir de este momento, utilizaremos este archivo para continuar con el trabajo.
ventas = pd.read_excel('C:/UNINORTE/VC/Proyecto2/datos.xlsx')
ventas.head(5)
| lat | long | id | date | category | location | mode | price | details | description | surface | rooms | baths | park | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | NaN | -0.000105 | 6416237 | 2021-06-20 | Apartamento | Bogotá Galerias | Venta | $148.000.000 | ['Área Const.:\r 44,00 ... | ApartamentoInteriorPrimerPisoremodeladoSalaCom... | _x000D_44,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_1_x000D__x000D_ | _x000D__x000D_Sinespecificar_x000D__x000D_ |
| 1 | NaN | -0.000105 | 6416237 | 2021-06-20 | Apartamento | Bogotá Galerias | Venta | $148.000.000 | ['Área Const.:\r 44,00 ... | ApartamentoInteriorPrimerPisoremodeladoSalaCom... | _x000D_44,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_1_x000D__x000D_ | _x000D__x000D_Sinespecificar_x000D__x000D_ |
| 2 | NaN | -0.000105 | 6416237 | 2021-06-20 | Apartamento | Bogotá Galerias | Venta | $148.000.000 | ['Área Const.:\r 44,00 ... | ApartamentoInteriorPrimerPisoremodeladoSalaCom... | _x000D_44,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_1_x000D__x000D_ | _x000D__x000D_Sinespecificar_x000D__x000D_ |
| 3 | NaN | -0.000105 | 6416237 | 2021-06-20 | Apartamento | Bogotá Galerias | Venta | $148.000.000 | ['Área Const.:\r 44,00 ... | ApartamentoInteriorPrimerPisoremodeladoSalaCom... | _x000D_44,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_1_x000D__x000D_ | _x000D__x000D_Sinespecificar_x000D__x000D_ |
| 4 | NaN | NaN | 6461572 | 2021-07-25 | Apartamento | Pereira El nogal | Venta | $215.000.000 | ['Área Const.:\r 65,00 ... | FantásticoApartamentoubicadoenelclubresidencia... | _x000D_65,00m²_x000D__x000D_ | _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ | _x000D__x000D_Baños:_x000D_2_x000D__x000D_ | _x000D__x000D_Parqueaderos:1_x000D__x000D_ |
Limpieza de datos#
# Reemplazar "Aconsultar" por NaN en la columna price
ventas['price'] = ventas['price'].replace('Aconsultar', np.nan)
# Eliminar el signo $, los puntos y los espacios en blanco de la columna price
ventas['price'] = ventas['price'].str.replace('[\$,\. ]', '', regex=True)
# Convertir la columna price a tipo numérico
ventas['price'] = pd.to_numeric(ventas['price'])
# Convertir la columna date a formato de fecha
ventas['date'] = pd.to_datetime(ventas['date'])
# Establecer valores fuera del rango válido como NaN en la columna 'lat', es decir, todos lo valores que esten fuera de las latitudes mínima y máxima de Colombia
ventas.loc[(ventas['lat'] < -4.227) | (ventas['lat'] > 12.450), 'lat'] = np.nan
# Establecer valores fuera del rango válido como NaN en la columna 'long', es decir, todos lo valores que esten fuera de las longitudes mínima y máxima de Colombia
ventas.loc[(ventas['long'] < -79.000) | (ventas['long'] > -67.000), 'long'] = np.nan
# Dividir la columna 'location' en dos nuevas columnas: 'ciudad' y 'barrio'
ventas[['ciudad', 'barrio']] = ventas['location'].str.split(' ', n=1, expand=True)
# Eliminar la columna 'location'
ventas.drop(columns=['location'], inplace=True)
# Eliminar "_x000D_" de las columnas especificadas
cols_to_clean = ['surface', 'rooms', 'baths', 'park']
for col in cols_to_clean:
ventas[col] = ventas[col].str.replace('_x000D_', '', regex=False)
# Eliminar "m²", puntos y comas de la columna 'surface', convertir a formato numérico y dividir por 100 para que se tenga en cuenta los decimales
ventas['surface'] = ventas['surface'].str.replace('m²', '', regex=False).str.replace('.', '', regex=False).str.replace(',', '', regex=False).astype(float) / 100
# Reemplazar "Sinespecificar" por NaN y eliminar "Habitaciones:" de la columna 'rooms'
ventas['rooms'] = ventas['rooms'].replace('Sinespecificar', np.nan).str.replace('Habitaciones:', '', regex=False).astype(float)
# Reemplazar "Sinespecificar" por NaN y eliminar "Baños:" de la columna 'baths'
ventas['baths'] = ventas['baths'].replace('Sinespecificar', np.nan).str.replace('Baños:', '', regex=False).astype(float)
# Limpiar y transformar la columna 'park'
ventas['park'] = ventas['park'].replace('Sinespecificar', '0')\
.str.replace('Parqueaderos:', '', regex=False)\
.replace('Másde10', '11')\
.astype(float)
# Eliminar filas donde 'mode' es "Arriendo"
ventas = ventas[ventas['mode'] != 'Arriendo']
# Eliminar la columna 'mode'
ventas.drop(columns=['mode'], inplace=True)
# Palabras a eliminar
words_to_remove = ['VENTA', 'VENDO', 'APTO', 'BARRIO']
# Eliminar palabras específicas y repetición del nombre de la ciudad
ventas['barrio'] = ventas.apply(lambda row: ' '.join(word for word in (row['barrio'] or '').split() if word.upper() not in words_to_remove and word.upper() != row['ciudad'].upper()), axis=1)
A continuación, trataremos por separado la columna details, dado que tiene muchos datos útiles para el análisis, pero están juntos dentro de una misma columna, de forma que no se pueden interpretar correctamente.
# Crear un nuevo DataFrame solo con la columna 'details'
nuevo_df = pd.DataFrame(ventas['details'])
# Dividir la columna 'details' en varias columnas separando el texto por '''
nuevo_df = nuevo_df['details'].str.split("'", expand=True)
# Renombrar las columnas del nuevo DataFrame
nuevo_df.columns = [f'detalle_{i}' for i in range(nuevo_df.shape[1])]
# Lista de columnas a eliminar
columnas_a_eliminar = ['detalle_0', 'detalle_2', 'detalle_4', 'detalle_6', 'detalle_8', 'detalle_10', 'detalle_12', 'detalle_14', 'detalle_16', 'detalle_18', 'detalle_20']
# Eliminar las columnas especificadas
nuevo_df.drop(columns=columnas_a_eliminar, inplace=True)
# Nombres de las columnas del nuevo DataFrame
nombres_columnas = ["Área privada", "Área Const.", "Precio m²", "Admón", "Estrato", "Estado", "Antigüedad", "Piso No", "Tipo de Apartamento", "Sector"]
# Crear el nuevo DataFrame con las columnas especificadas
df_final = pd.DataFrame(columns=nombres_columnas)
# Función para extraer el valor asociado a un nombre de columna en una fila del DataFrame original
def extraer_valor(fila, nombre_columna):
for elemento in fila:
if isinstance(elemento, str) and nombre_columna.lower() in elemento.lower():
return elemento.split(':')[1].strip()
return None
# Llenar el nuevo DataFrame buscando los valores correspondientes en cada fila de nuevo_df
for nombre_columna in nombres_columnas:
df_final[nombre_columna] = nuevo_df.apply(lambda fila: extraer_valor(fila, nombre_columna), axis=1)
df_final.head()
| Área privada | Área Const. | Precio m² | Admón | Estrato | Estado | Antigüedad | Piso No | Tipo de Apartamento | Sector | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | None | \r 44,00 m² | \r 3.363.636/m² | None | \r 4\r \r | None | \r 9 a 15 años | None | None | Ver Mapa |
| 1 | None | \r 44,00 m² | \r 3.363.636/m² | None | \r 4\r \r | None | \r 9 a 15 años | None | None | Ver Mapa |
| 2 | None | \r 44,00 m² | \r 3.363.636/m² | None | \r 4\r \r | None | \r 9 a 15 años | None | None | Ver Mapa |
| 3 | None | \r 44,00 m² | \r 3.363.636/m² | None | \r 4\r \r | None | \r 9 a 15 años | None | None | Ver Mapa |
| 4 | None | \r 65,00 m² | \r 3.307.692/m² | None | \r 4\r \r | None | None | None | None | Ver Mapa |
A continuación, concatenar esta nueva información con nuestro dataframe ‘ventas’
# Concatenar los DataFrames a lo largo del eje de las columnas
ventas = pd.concat([ventas, df_final], axis=1)
Eliminaremos las siguientes columnas:
‘details’: La información contenida en esta columna ya está divida en otras columnas (pasos realizados anteriormente).
‘description’: Es un valor único para cada vivienda y es subjetivo.
‘date’: Es la fecha en la que se cargó la información de la vivienda a la pagina, lo cual no influye en el costo de la vivienda.
‘id’: Es el código asignado a la vivienda al momento del registro y es un valor único, no influye en el costo de la vivienda.
‘Precio m²’: Es una columna calculada a partir de la columna ‘price’ y ‘Área const.’.
# Eliminar la columna 'details'
ventas.drop(columns=['details'], inplace=True)
# Eliminar la columna 'description'
ventas.drop(columns=['description'], inplace=True)
# Eliminar la columna 'date'
ventas.drop(columns=['date'], inplace=True)
# Eliminar la columna 'Precio m²'
ventas.drop(columns=['Precio m²'], inplace=True)
# Limpieza y conversión de las columnas "Área privada" y "Área Const." en el DataFrame ventas
for columna in ["Área privada", "Área Const."]:
ventas[columna] = (ventas[columna]
.str.replace('m²', '', regex=False)
.str.replace('.', '', regex=False)
.str.replace(',', '', regex=False)
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.strip()
.astype(float) / 100)
# Limpieza de la columna "Admón"
ventas['Admón'] = (ventas['Admón']
.str.replace('$', '', regex=False)
.str.replace(',', '', regex=False)
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.replace('Incluida', '0', regex=False)
.str.strip()
.astype(float))
# Limpieza de la columna "Estrato"
ventas['Estrato'] = (ventas['Estrato']
.str.strip()
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.replace('Campestre', np.nan)
.replace(' Campestre ', np.nan)
.astype(float))
# Limpieza de la columna "Estado"
ventas['Estado'] = (ventas['Estado']
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.strip())
# Mostrar los valores únicos en la columna "Estado"
valores_unicos_estado = ventas['Estado'].unique()
print(valores_unicos_estado)
[None 'Bueno' 'Excelente' 'Remodelar']
A continuación, cambiaremos los valores de la columna estado para pasarlos a números, teniendo en cuenta que esto es una calificación de la vivienda.
# Cambiar los valores en la columna "Estado"
ventas['Estado'] = ventas['Estado'].replace({'Remodelar': 3, 'Bueno': 4, 'Excelente': 5})
# Cambiar el nombre de la columna "Antigüedad" a "Antiguedad"
ventas.rename(columns={'Antigüedad': 'Antiguedad'}, inplace=True)
# Limpieza de la columna "Antigüedad"
ventas['Antiguedad'] = (ventas['Antiguedad']
.str.replace('\\r', '', regex=False)
.str.strip())
# Limpieza de la columna "Piso No"
ventas['Piso No'] = (ventas['Piso No']
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.replace('º', '', regex=False)
.str.replace('ª', '', regex=False)
.str.strip()
.replace('Otros', np.nan)
.astype(float))
# Limpieza de la columna "Tipo de Apartamento"
ventas['Tipo de Apartamento'] = (ventas['Tipo de Apartamento']
.str.replace('\r', '', regex=False)
.str.replace('\\r', '', regex=False)
.str.strip())
# Cambiar valores en 'Tipo de Apartamento' a 'No Aplica' cuando 'category' es 'Casa'
ventas.loc[ventas['category'] == 'Casa', 'Tipo de Apartamento'] = 'No Aplica'
# Limpieza de la columna "Sector"
ventas['Sector'] = (ventas['Sector']
.str.replace('Ver Mapa', '', regex=False)
.replace('', np.nan) # Reemplazar cadenas vacías resultantes con NaN
.str.strip())
# Restablecer el índice del DataFrame
ventas = ventas.reset_index(drop=True)
Eliminar columnas duplicadas:
Los duplicados se eliminarán de acuerdo con los id de vivienda duplicados y teniendo en cuenta que las filas que queden sean las más completas.
# Eliminar filas duplicadas manteniendo la fila con más datos completos - Para aseguridad de que un mismo inmueble no tuviera 2 id diferentes
ventas = (ventas.sort_values(by=ventas.columns.tolist(), na_position='last')
.drop_duplicates(subset=['id'], keep='first')
.drop_duplicates())
Eliminar columna:
‘id’: Es el código asignado a la vivienda al momento del registro y es un valor único, no influye en el costo de la vivienda.
# Eliminar la columna 'id'
ventas.drop(columns=['id'], inplace=True)
ventas.head(10)
| lat | long | category | price | surface | rooms | baths | park | ciudad | barrio | Área privada | Área Const. | Admón | Estrato | Estado | Antiguedad | Piso No | Tipo de Apartamento | Sector | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 407717 | -3.556624 | NaN | Apartamento | 197000000.0 | 64.0 | 2.0 | 2.0 | 1.0 | Cali | Ciudad Bochalema | 64.0 | 64.0 | 178000.0 | 4.0 | NaN | 1 a 8 años | NaN | None | Ciudad Bochalema |
| 407715 | -2.889093 | NaN | Casa | 650000000.0 | 192.0 | 5.0 | 3.0 | 0.0 | Bogotá | Antiguo Copihue | 192.0 | 192.0 | NaN | 3.0 | NaN | 1 a 8 años | NaN | No Aplica | Zona Norte |
| 407713 | -2.577413 | -73.038536 | Apartaestudio | 70000000.0 | 30.0 | 1.0 | 1.0 | 0.0 | Bogotá | Las Lomas | 30.0 | 30.0 | 45000.0 | 2.0 | NaN | 16 a 30 años | NaN | None | Las Lomas |
| 407710 | -2.193166 | NaN | Apartamento | 235000000.0 | 56.0 | 3.0 | 2.0 | 1.0 | Medellín | Calasanz Occidente | 56.0 | 56.0 | 192139.0 | 3.0 | NaN | None | 13.0 | None | NaN |
| 407708 | -2.037440 | NaN | Apartaestudio | 139000000.0 | 43.0 | 1.0 | 1.0 | 1.0 | Barranquilla | Ciudad Jardín | 43.0 | 43.0 | 145000.0 | 4.0 | NaN | None | 4.0 | None | NaN |
| 407706 | -1.054628 | -73.300781 | Apartamento | 260000000.0 | 115.0 | 4.0 | 2.0 | 0.0 | Medellín | Centro | NaN | 115.0 | NaN | 4.0 | 4.0 | 9 a 15 años | 3.0 | None | Centro |
| 407703 | -0.341669 | -78.530228 | Apartamento | 175000000.0 | 63.0 | 3.0 | 2.0 | 1.0 | Cartagena | Ciudad Jardin | 63.0 | 63.0 | 115000.0 | 3.0 | NaN | None | 7.0 | None | NaN |
| 407700 | -0.181263 | NaN | Apartamento | 153000000.0 | 74.0 | 3.0 | 2.0 | 1.0 | Barranquilla | Miramar | 74.0 | 74.0 | 190000.0 | 4.0 | NaN | None | 5.0 | None | NaN |
| 407699 | -0.175781 | -77.255859 | Casa | 390000000.0 | 160.0 | 6.0 | 3.0 | 1.0 | Medellín | Occidente | 160.0 | 160.0 | NaN | 3.0 | 4.0 | 16 a 30 años | 1.0 | No Aplica | Occidente |
| 407696 | -0.148553 | NaN | Apartamento | 145000000.0 | 54.0 | 3.0 | 1.0 | 0.0 | Bogotá | Ciudad Tintal | 54.0 | 54.0 | NaN | 3.0 | NaN | 9 a 15 años | NaN | None | Ciudad Tintal |
ventas = ventas.reset_index(drop=True)
ventas['Estrato'] = ventas['Estrato'].astype('category')
ventas['Estado'] = ventas['Estado'].astype('category')
ventas['category'] = ventas['category'].astype('category')
ventas.head()
| lat | long | category | price | surface | rooms | baths | park | ciudad | barrio | Área privada | Área Const. | Admón | Estrato | Estado | Antiguedad | Piso No | Tipo de Apartamento | Sector | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -3.556624 | NaN | Apartamento | 197000000.0 | 64.0 | 2.0 | 2.0 | 1.0 | Cali | Ciudad Bochalema | 64.0 | 64.0 | 178000.0 | 4.0 | NaN | 1 a 8 años | NaN | None | Ciudad Bochalema |
| 1 | -2.889093 | NaN | Casa | 650000000.0 | 192.0 | 5.0 | 3.0 | 0.0 | Bogotá | Antiguo Copihue | 192.0 | 192.0 | NaN | 3.0 | NaN | 1 a 8 años | NaN | No Aplica | Zona Norte |
| 2 | -2.577413 | -73.038536 | Apartaestudio | 70000000.0 | 30.0 | 1.0 | 1.0 | 0.0 | Bogotá | Las Lomas | 30.0 | 30.0 | 45000.0 | 2.0 | NaN | 16 a 30 años | NaN | None | Las Lomas |
| 3 | -2.193166 | NaN | Apartamento | 235000000.0 | 56.0 | 3.0 | 2.0 | 1.0 | Medellín | Calasanz Occidente | 56.0 | 56.0 | 192139.0 | 3.0 | NaN | None | 13.0 | None | NaN |
| 4 | -2.037440 | NaN | Apartaestudio | 139000000.0 | 43.0 | 1.0 | 1.0 | 1.0 | Barranquilla | Ciudad Jardín | 43.0 | 43.0 | 145000.0 | 4.0 | NaN | None | 4.0 | None | NaN |
ventas.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 169528 entries, 0 to 169527
Data columns (total 19 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 lat 169191 non-null float64
1 long 149730 non-null float64
2 category 169528 non-null category
3 price 169515 non-null float64
4 surface 169526 non-null float64
5 rooms 167914 non-null float64
6 baths 168167 non-null float64
7 park 169528 non-null float64
8 ciudad 169528 non-null object
9 barrio 169528 non-null object
10 Área privada 120204 non-null float64
11 Área Const. 169526 non-null float64
12 Admón 105780 non-null float64
13 Estrato 167101 non-null category
14 Estado 98550 non-null category
15 Antiguedad 139813 non-null object
16 Piso No 101698 non-null float64
17 Tipo de Apartamento 57552 non-null object
18 Sector 134150 non-null object
dtypes: category(3), float64(11), object(5)
memory usage: 21.2+ MB
Análisis Descriptivo#
ventas.describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| lat | 169191.0 | 5.037213e+00 | 2.789628e+00 | -3.556624e+00 | 4.594397e+00 | 4.704000e+00 | 6.189920e+00 | 1.140061e+01 |
| long | 149730.0 | -7.485683e+01 | 8.842583e-01 | -7.853023e+01 | -7.556703e+01 | -7.419706e+01 | -7.406600e+01 | -6.713100e+01 |
| price | 169515.0 | 1.923391e+10 | 5.534035e+12 | 1.530000e+02 | 2.500000e+08 | 3.950000e+08 | 6.800000e+08 | 1.989188e+15 |
| surface | 169526.0 | 2.467323e+03 | 3.904603e+05 | 1.000000e+00 | 6.800000e+01 | 1.000000e+02 | 1.720000e+02 | 1.000000e+08 |
| rooms | 167914.0 | 3.278226e+00 | 2.059622e+00 | 1.000000e+00 | 3.000000e+00 | 3.000000e+00 | 4.000000e+00 | 2.540000e+02 |
| baths | 168167.0 | 2.719404e+00 | 1.523848e+00 | 1.000000e+00 | 2.000000e+00 | 2.000000e+00 | 3.000000e+00 | 2.530000e+02 |
| park | 169528.0 | 1.293946e+00 | 1.237870e+00 | 0.000000e+00 | 1.000000e+00 | 1.000000e+00 | 2.000000e+00 | 1.100000e+01 |
| Área privada | 120204.0 | 1.948304e+03 | 5.486953e+05 | 1.000000e+00 | 6.700000e+01 | 1.000000e+02 | 1.720000e+02 | 1.900000e+08 |
| Área Const. | 169526.0 | 2.467323e+03 | 3.904603e+05 | 1.000000e+00 | 6.800000e+01 | 1.000000e+02 | 1.720000e+02 | 1.000000e+08 |
| Admón | 105780.0 | 1.824785e+06 | 3.205592e+07 | -1.794967e+09 | 1.750000e+05 | 3.150000e+05 | 5.600000e+05 | 1.900000e+09 |
| Piso No | 101698.0 | 4.193730e+00 | 3.111030e+00 | 1.000000e+00 | 2.000000e+00 | 3.000000e+00 | 5.000000e+00 | 1.600000e+01 |
Conclusiónes parciales de la información anteriór:
Price: Hay una enorme variabilidad con valores desde 152 hasta 1.989188e+15. La desviación estándar es también extremadamente alta, indicando la presencia de valores atípicos significativos que afectan la media y la dispersión.
Surface y Área Const: Ambas variables tienen el mismo resumen estadístico, lo que podría indicar duplicidad o un error en el reporte. El valor máximo de 100.000.000 y el mínimo es 1, son valores inusuales para superficies habitacionales, lo que indica la presencia de valores atípicos.
baths: El valor máximo de 253 para baños es improbable para propiedades residenciales y sugiere un error.
Admón: Se evidencian datos atípicos y errores, dado que tiene valores negativos y un máximo extremadamente alto, además de una gran desviación estándar.
Análisis de datos atípicos#
ventas = pd.read_csv('C:/UNINORTE/VC/Proyecto2/Archivos_Finales/datos2.csv')
ventas.describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| lat | 169191.0 | 5.037213e+00 | 2.789628e+00 | -3.556624e+00 | 4.594397e+00 | 4.704000e+00 | 6.189920e+00 | 1.140061e+01 |
| long | 149730.0 | -7.485683e+01 | 8.842583e-01 | -7.853023e+01 | -7.556703e+01 | -7.419706e+01 | -7.406600e+01 | -6.713100e+01 |
| price | 169515.0 | 1.923391e+10 | 5.534035e+12 | 1.530000e+02 | 2.500000e+08 | 3.950000e+08 | 6.800000e+08 | 1.989188e+15 |
| surface | 169526.0 | 2.467323e+03 | 3.904603e+05 | 1.000000e+00 | 6.800000e+01 | 1.000000e+02 | 1.720000e+02 | 1.000000e+08 |
| rooms | 167914.0 | 3.278226e+00 | 2.059622e+00 | 1.000000e+00 | 3.000000e+00 | 3.000000e+00 | 4.000000e+00 | 2.540000e+02 |
| baths | 168167.0 | 2.719404e+00 | 1.523848e+00 | 1.000000e+00 | 2.000000e+00 | 2.000000e+00 | 3.000000e+00 | 2.530000e+02 |
| park | 169528.0 | 1.293946e+00 | 1.237870e+00 | 0.000000e+00 | 1.000000e+00 | 1.000000e+00 | 2.000000e+00 | 1.100000e+01 |
| Área privada | 120204.0 | 1.948304e+03 | 5.486953e+05 | 1.000000e+00 | 6.700000e+01 | 1.000000e+02 | 1.720000e+02 | 1.900000e+08 |
| Área Const. | 169526.0 | 2.467323e+03 | 3.904603e+05 | 1.000000e+00 | 6.800000e+01 | 1.000000e+02 | 1.720000e+02 | 1.000000e+08 |
| Admón | 105780.0 | 1.824785e+06 | 3.205592e+07 | -1.794967e+09 | 1.750000e+05 | 3.150000e+05 | 5.600000e+05 | 1.900000e+09 |
| Estrato | 167101.0 | 4.354959e+00 | 1.280686e+00 | 1.000000e+00 | 3.000000e+00 | 4.000000e+00 | 6.000000e+00 | 6.000000e+00 |
| Estado | 98550.0 | 4.492532e+00 | 5.439049e-01 | 3.000000e+00 | 4.000000e+00 | 5.000000e+00 | 5.000000e+00 | 5.000000e+00 |
| Piso No | 101698.0 | 4.193730e+00 | 3.111030e+00 | 1.000000e+00 | 2.000000e+00 | 3.000000e+00 | 5.000000e+00 | 1.600000e+01 |
Dado que la columna ‘Admón’ tiene valores negativos y esto en la práctica no tiene sentido, estos valores serán reemplazados por nulos.
# Replace negative values with NaN in the 'Admón' column
ventas['Admón'] = ventas['Admón'].apply(lambda x: np.nan if x < 0 else x)
Contar valores ‘cero’ en cada columna.
# Count zero values in each column
zero_counts = (ventas == 0).sum()
# Print the counts of zero values
print(zero_counts)
lat 19342
long 0
category 0
price 0
surface 0
rooms 0
baths 0
park 40868
ciudad 0
barrio 0
Área privada 0
Área Const. 0
Admón 1147
Estrato 0
Estado 0
Antiguedad 0
Piso No 0
Tipo de Apartamento 0
Sector 0
dtype: int64
# Replace cero values with NaN in the 'lat' column
ventas['lat'] = ventas['lat'].replace(0, np.nan)
Funciones#
Función para detección de valores atípicos mediante el rango intercuantil (IQR)
def detect_outliers(df, column, lower_percentile, upper_percentile):
# Calcular el primer y el cuartil superior basado en los percentiles proporcionados
Q1 = df[column].quantile(lower_percentile)
Q3 = df[column].quantile(upper_percentile)
# Calcular el rango intercuartílico (IQR)
IQR = Q3 - Q1
# Definir los límites para los valores atípicos
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# Identificar los valores atípicos
outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)][column]
# Mostrar un análisis descriptivo de los valores atípicos
print("Análisis descriptivo de valores atípicos en '{}':".format(column))
print(outliers.describe().to_frame().T)
# Calcular y mostrar el porcentaje de valores atípicos
porcentaje = (len(outliers) / len(df)) * 100
print("\nPorcentaje de valores atípicos: {:.2f}%".format(porcentaje))
cant=len(outliers)
print("\nCantidad de valores atípicos: {}".format(cant))
# Retornar los valores atípicos y sus límites para uso adicional si es necesario
return outliers, lower_bound, upper_bound, cant
Función para detección de valores atípicos mediante las puntuaciones \(Z\)
def detect_outliers_zscore(data, column):
# Extraer la columna deseada del DataFrame
datacol = data[column].dropna() # Asegúrate de eliminar NaNs para el cálculo
thres = 3 # Umbral para la puntuación Z
# Calcular la media y la desviación estándar
mean = np.mean(datacol)
std = np.std(datacol)
# Detectar outliers
outliers = []
for i in datacol:
z_score = (i - mean) / std
if np.abs(z_score) > thres:
outliers.append(i)
# Convertir la lista de outliers en un DataFrame para análisis
outliers_df = pd.DataFrame(outliers, columns=['Outliers'])
# Realizar un análisis descriptivo de los outliers detectados
print("Análisis descriptivo de los valores atípicos en '{}':".format(column))
if not outliers_df.empty:
print(outliers_df.describe().T)
# Calcular y mostrar el porcentaje de outliers
porcentaje_outliers = (len(outliers) / len(datacol)) * 100
print("\nPorcentaje de valores atípicos en '{}': {:.2f}%".format(column, porcentaje_outliers))
# Opcional: devolver el DataFrame de outliers y otros datos para uso futuro
return outliers_df, mean, std, porcentaje_outliers
Función para prueba Rosner
def rosner_test(data, k):
# Convertir los datos a un array de NumPy y asegurarse de manejar los índices correctamente
data = np.array(data)
indices = np.arange(len(data)) # Crear un array de índices
mean = np.mean(data)
std = np.std(data)
outliers_pr = []
outlier_indices = []
for _ in range(k):
z_scores = np.abs((data - mean) / std)
max_z = np.max(z_scores)
if max_z > 3: # Umbral típico de Z-score para outliers
max_index = np.argmax(z_scores)
outliers_pr.append(data[max_index])
outlier_indices.append(indices[max_index]) # Guardar el índice del outlier
data = np.delete(data, max_index) # Eliminar el outlier para la siguiente iteración
indices = np.delete(indices, max_index) # Eliminar el índice del outlier
mean = np.mean(data) # Recalcular la media
std = np.std(data) # Recalcular la desviación estándar
else:
break
return outliers_pr, outlier_indices
Surface y Area Const#
# Gráfico de dispersión
fig = px.scatter(ventas, x='surface', y='Área Const.', title='Comparación de Surface y Área Const.',
labels={'surface': 'Surface (m²)', 'Área Const.': 'Área Const. (m²)'})
fig.update_traces(marker=dict(color=custom_colors[0]))
fig.update_layout(
xaxis_title='Surface (m²)',
yaxis_title='Área Const. (m²)'
)
fig.show()
# Crear el gráfico de caja
fig = px.box(ventas, y=['surface', 'Área Const.'], title='Box Plot de Surface y Área Const.',
labels={'variable': 'Variable', 'value': 'Superficie (m²)'},
color='variable',
color_discrete_sequence=custom_colors)
# Mostrar el gráfico
fig.show()
ventas_suryare = ventas.dropna(subset=['surface', 'Área Const.']) # Elimina filas donde cualquiera de las dos columnas es NaN
# Calcular la correlación de Pearson entre las dos columnas
correlation = ventas_suryare['surface'].corr(ventas_suryare['Área Const.'])
# Imprimir el coeficiente de correlación
print("El coeficiente de correlación de Pearson entre 'surface' y 'Área Const.' es:", correlation)
El coeficiente de correlación de Pearson entre 'surface' y 'Área Const.' es: 0.9999999999999999
Con una correlación de 0.99 y como es evidente en el gráfico de dispersión y en el grafico de caja, estas 2 columnas son iguales, por ende, eliminaremos una de ellas, en este caso ‘surface’.
# Eliminar la columna 'Surface'
ventas = ventas.drop(columns=['surface'])
# Renombrar las columnas 'Área Const.' y 'Área privada'
ventas = ventas.rename(columns={
'Área Const.': 'area_const',
'Área privada': 'area_privada'
})
# Colores personalizados
color_histograma = custom_colors[3]
color_linea = custom_colors[4]
color_box = custom_colors[0]
# Crear una figura con subplots, especificando el espacio entre subplots
fig = make_subplots(rows=1, cols=2,
subplot_titles=('Histograma de área construida', 'Box Plot de Área Construida'),
column_widths=[0.5, 0.5], # Ajustar según sea necesario
horizontal_spacing=0.2) # Espacio entre las columnas
# Agregar histograma al primer subplot
fig.add_trace(
go.Histogram(
x=ventas['area_const'],
opacity=0.8,
marker=dict(color=color_histograma, line=dict(color=color_linea, width=1.5)),
name='Área Construida'
),
row=1, col=1
)
# Agregar box plot al segundo subplot
fig.add_trace(
go.Box(
y=ventas['area_const'],
marker_color=color_box,
name='Área Construida'
),
row=1, col=2
)
# Actualizar los títulos de los ejes y el layout general
fig.update_layout(
title_text='Distribución de Área Construida',
showlegend=False
)
fig.update_xaxes(title_text='Área Construida (m²)', row=1, col=1)
fig.update_yaxes(title_text='Frecuencia', row=1, col=1)
fig.update_yaxes(title_text='Área Construida (m²)', row=1, col=2)
# Mostrar la figura
fig.show()
Inicialmente colocaremos como nulos todos los valores de área construida que sean superiores a \(2.500 m²\), teniendo en cuenta que los datos que tenemos corresponden a casas, apartamentos y apartaestudios y no es posible que alguno de estos inmuebles tenga un área construida superior a ese valor. Estos corresponden al \(0.31%\) de nuestros datos.
Adicionalmente, según las siguientes noticias de medios de comunicación como Noticias Caracol y El Tiempo, las casas más grandes de Colombia no superan los \(2.500 m²\) ni el valor de \(18\) mil millones de pesos colombianos:
# Contar los valores mayores que 2500
valores_mayores_2500 = ventas[ventas['area_const'] > 2500]['area_const'].count()
# Calcular el total de entradas no nulas en la columna 'area_const'
total_entradas = ventas['area_const'].notnull().sum()
# Calcular el porcentaje
porcentaje_mayores_2500 = (valores_mayores_2500 / total_entradas) * 100
# Imprimir los resultados
print("Cantidad de valores mayores que 2500 en 'area_const':", valores_mayores_2500)
print("Porcentaje de valores mayores que 2500 en 'area_const': {:.2f}%".format(porcentaje_mayores_2500))
Cantidad de valores mayores que 2500 en 'area_const': 524
Porcentaje de valores mayores que 2500 en 'area_const': 0.31%
# Marcar valores superiores a 2500 en 'area_const' como NaN
ventas.loc[ventas['area_const'] > 2500, 'area_const'] = np.nan
Continuamos con el análisis de datos atipicos para la columna ‘area_const’.
Detección de valores atípicos mediante las puntuaciones \(Z\)
outliers_df, mean, std, porc_outliers = detect_outliers_zscore(ventas, 'area_const')
Análisis descriptivo de los valores atípicos en 'area_const':
count mean std min 25% 50% 75% max
Outliers 2764.0 842.622012 349.465854 559.0 612.0 723.0 900.0 2500.0
Porcentaje de valores atípicos en 'area_const': 1.64%
Detección de valores atípicos mediante el rango intercuantil (IQR)
outliers, lower_bound, upper_bound, cant = detect_outliers(ventas, 'area_const', 0.25, 0.75)
Análisis descriptivo de valores atípicos en 'area_const':
count mean std min 25% 50% 75% \
area_const 12246.0 506.665418 251.797554 326.0 365.0 424.0 540.0
max
area_const 2500.0
Porcentaje de valores atípicos: 7.22%
Cantidad de valores atípicos: 12246
Prueba Rosner
outliers_pr, outlier_indices = rosner_test(ventas['area_const'].dropna(), k=cant)
# Imprimir comparación de cantidad de outliers
print("Después de la prueba Rosner, la cantidad de datos atípicos es:", len(outliers_pr))
print("Comparado con la cantidad de datos atípicos detectados por el método IQR que es:", cant)
Después de la prueba Rosner, la cantidad de datos atípicos es: 12246
Comparado con la cantidad de datos atípicos detectados por el método IQR que es: 12246
# Marcar como nulos los outliers detectados por la prueba Rosner en el DataFrame original
for idx in outliers.index:
if idx in ventas.index: # Verificar si el índice está en el DataFrame
ventas.at[idx, 'area_const'] = np.nan
# Verificar el cambio en el DataFrame
print("\nActualización completada en el DataFrame. Total de NaN ahora en 'area_const':", ventas['area_const'].isna().sum())
Actualización completada en el DataFrame. Total de NaN ahora en 'area_const': 12772
# Crear un histograma del área construida
fig = px.histogram(ventas, x='area_const', title='Histograma de área construida',
labels={'area_const': 'Área Construida'},
opacity=0.8, color_discrete_sequence=[color_histograma])
# Personalizar el gráfico
fig.update_layout(xaxis_title='Área Construida (m²)', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color=color_linea)
# Mostrar el gráfico
fig.show()
# Definir el color deseado para el box plot
color_box = '#1F3040'
# Crear el box plot de área construida
fig = px.box(ventas, y=['area_const'], title='Box Plot de Área Construida',
labels={'variable': 'Variable', 'value': 'Área Construida (m²)'},
color_discrete_sequence=[color_box])
# Personalizar el gráfico
fig.update_layout(xaxis_title='Variable', yaxis_title='Área Construida (m²)')
# Mostrar el gráfico
fig.show()
Área Privada#
# Colores personalizados
color_histograma = custom_colors[3]
color_linea = custom_colors[4]
color_box = custom_colors[0]
# Crear una figura con subplots
fig = make_subplots(rows=1, cols=2,
subplot_titles=('Distribución de Área Privada', 'Boxplot de Área Privada'),
column_widths=[0.5, 0.5], # Ajustar según sea necesario
horizontal_spacing=0.2) # Espacio entre las columnas
# Agregar histograma al primer subplot
fig.add_trace(
go.Histogram(
x=ventas['area_privada'],
opacity=0.8,
marker=dict(color=color_histograma, line=dict(color=color_linea, width=1.5)),
name='Área Privada'
),
row=1, col=1
)
# Agregar box plot al segundo subplot
fig.add_trace(
go.Box(
y=ventas['area_privada'],
marker_color=color_box,
name='Área Privada'
),
row=1, col=2
)
# Actualizar los títulos de los ejes y el layout general
fig.update_layout(
title_text='Análisis de Área Privada',
showlegend=False
)
fig.update_xaxes(title_text='Área Privada (m²)', row=1, col=1)
fig.update_yaxes(title_text='Frecuencia', row=1, col=1)
fig.update_yaxes(title_text='Área Privada (m²)', row=1, col=2)
# Mostrar la figura
fig.show()
Basados en la información de las noticias compartidas en el análisis de la variable área construida, eliminaremos todos los valores superiores a \(2.500 m^2\).
# Contar los valores mayores que 2500
valores_mayores_2500 = ventas[ventas['area_privada'] > 2500]['area_privada'].count()
# Calcular el total de entradas no nulas en la columna 'area_privada'
total_entradas = ventas['area_privada'].notnull().sum()
# Calcular el porcentaje
porcentaje_mayores_2500 = (valores_mayores_2500 / total_entradas) * 100
# Imprimir los resultados
print("Cantidad de valores mayores que 2500 en 'area_privada':", valores_mayores_2500)
print("Porcentaje de valores mayores que 2500 en 'area_privada': {:.2f}%".format(porcentaje_mayores_2500))
Cantidad de valores mayores que 2500 en 'area_privada': 509
Porcentaje de valores mayores que 2500 en 'area_privada': 0.42%
# Marcar valores superiores a 2500 en 'area_privada' como NaN
ventas.loc[ventas['area_privada'] > 2500, 'area_privada'] = np.nan
Detección de valores atípicos mediante las puntuaciones \(Z\)
outliers_df, mean, std, porc_outliers = detect_outliers_zscore(ventas, 'area_privada')
Análisis descriptivo de los valores atípicos en 'area_privada':
count mean std min 25% 50% 75% max
Outliers 1819.0 1066.801539 424.441119 628.0 742.0 902.0 1300.0 2500.0
Porcentaje de valores atípicos en 'area_privada': 1.52%
Detección de valores atípicos mediante el rango intercuantil (IQR)
outliers, lower_bound, upper_bound, cant = detect_outliers(ventas, 'area_privada', 0.25, 0.75)
Análisis descriptivo de valores atípicos en 'area_privada':
count mean std min 25% 50% 75% max
area_privada 8647.0 559.02965 334.01563 325.0 369.0 437.0 592.5 2500.0
Porcentaje de valores atípicos: 5.10%
Cantidad de valores atípicos: 8647
Prueba Rosner
outliers_pr, outlier_indices = rosner_test(ventas['area_privada'].dropna(), k=cant)
# Imprimir comparación de cantidad de outliers
print("Después de la prueba Rosner, la cantidad de datos atípicos es:", len(outliers_pr))
print("Comparado con la cantidad de datos atípicos detectados por el método IQR que es:", cant)
Después de la prueba Rosner, la cantidad de datos atípicos es: 8647
Comparado con la cantidad de datos atípicos detectados por el método IQR que es: 8647
# Marcar como nulos los outliers detectados por la prueba Rosner en el DataFrame original
for idx in outliers.index:
if idx in ventas.index: # Verificar si el índice está en el DataFrame
ventas.at[idx, 'area_privada'] = np.nan
# Verificar el cambio en el DataFrame
print("\nActualización completada en el DataFrame. Total de NaN ahora en 'area_privada':", ventas['area_privada'].isna().sum())
Actualización completada en el DataFrame. Total de NaN ahora en 'area_privada': 58480
# Marcar como NaN los valores menores a 2 en 'area_privada'
ventas.loc[ventas['area_privada'] < 2, 'area_privada'] = np.nan
# Comparar 'area_privada' con 'area_const' y ajustar según la condición
ventas.loc[ventas['area_privada'] > ventas['area_const'], 'area_privada'] = ventas['area_const']
# Histograma de Área Privada
fig_histogram = px.histogram(ventas, x='area_privada', title='Distribución de Área Privada',
color_discrete_sequence=[color_histograma])
fig_histogram.update_layout(xaxis_title='Área Privada (m²)', yaxis_title='Frecuencia')
fig_histogram.update_traces(marker_line_width=1, marker_line_color=color_linea)
fig_histogram.show()
# Boxplot de Área Privada
fig_boxplot = px.box(ventas, y='area_privada', title='Boxplot de Área Privada',
color_discrete_sequence=[color_box])
fig_boxplot.update_layout(yaxis_title='Área Privada (m²)')
fig_boxplot.show()
#ventas.to_csv('datos3.csv', index=False)